﻿using System;
using System.Collections;
using System.Collections.Generic;

using Cache = Sencha.ReaderWriterLockCache<System.Object, Sencha.Serialization.Contracts.DataContract>;

namespace Sencha.Serialization.Contracts
{
	internal static class DataContractCache
	{
		private static readonly IList<TypeDescription> s_Primitives;
		private static readonly Cache s_Cache = new Cache(100);
		private static readonly DataContract s_DefaultContract;
		private static readonly DataContract s_AdHocContract;

		public static DataContract DefaultContract { get { return s_DefaultContract; } }
		public static DataContract AdHocContract { get { return s_AdHocContract; } }

		static DataContractCache()
		{
			Converter<Type, TypeDescription> conversion = t => new TypeDescription(t)
			{
				Name = t.Name,
				TypeFullName = t.FullName,
				IsBuiltIn = !t.IsArray,
				IsSerializable = true,
				IsArray = t.IsArray,
				IsPrimitive = (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>) ? Nullable.GetUnderlyingType(t) : t).IsPrimitive,
				IsReferenceType = !t.IsValueType,
				IsNumeric = Array.IndexOf(TypeDescription.Numbers, (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>) ? Nullable.GetUnderlyingType(t) : t)) != -1,
				IsObjectType = t == typeof(object),
				IsNullable = t.IsClass || (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)),
				IsValueType = t.IsValueType,
				IsEnumerable = t.IsArray,
				IsConvertible = t.IsValueType || t == typeof(string)
			};

			Type[] prims = new Type[] { 
                typeof(Byte), typeof(Decimal), typeof(Double), typeof(Single),typeof(Int16), typeof(Int32), typeof(Int64), typeof(SByte),  typeof(UInt16), typeof(UInt32), typeof(UInt64), typeof(Boolean), typeof(Char), typeof(DateTime), typeof(TimeSpan),typeof(DateTimeOffset), typeof(Guid), typeof(String), 
				typeof(Byte?), typeof(Decimal?), typeof(Double?), typeof(Single?),typeof(Int16?), typeof(Int32?), typeof(Int64?), typeof(SByte?),  typeof(UInt16?), typeof(UInt32?), typeof(UInt64?), typeof(Boolean?), typeof(Char?), typeof(DateTime?), typeof(TimeSpan?),typeof(DateTimeOffset?), typeof(Guid?),
				//typeof(Byte[]), typeof(Decimal[]), typeof(Double[]), typeof(Single[]),typeof(Int16[]), typeof(Int32[]), typeof(Int64[]), typeof(SByte[]),  typeof(UInt16[]), typeof(UInt32[]), typeof(UInt64[]), typeof(Boolean[]), typeof(Char[]), typeof(DateTime[]), typeof(TimeSpan[]),typeof(DateTimeOffset[]), typeof(Guid[]), typeof(String[])
			};

			s_Primitives = Array.AsReadOnly(Array.ConvertAll(prims, conversion));

			s_DefaultContract = new DataContract();
			// add basic types
			s_DefaultContract.AddKnownType(typeof(object[]));
			s_DefaultContract.AddKnownType(typeof(ValueType));
			s_DefaultContract.AddKnownType(typeof(Enum));

			// add primitives
			for (int i = 0; i < s_Primitives.Count; i++)
			{
				var primitive = s_Primitives[i];
				var baseType = primitive.GetClrType().BaseType ?? typeof(object);
				var underType = primitive.IsNullable && primitive.IsValueType ? primitive.GetClrType().GetGenericArguments()[0] :
								primitive.IsArray ? primitive.GetClrType().GetElementType() : null;

				primitive.BaseType = s_DefaultContract.AddKnownType(baseType);
				if (baseType != typeof(object))
					s_DefaultContract.AddTypeHeir(primitive.BaseType, primitive);
				if (underType != null)
					primitive.UnderlyingType = s_DefaultContract.AddKnownType(underType);

				s_DefaultContract.AddTypeInfo(primitive);
			}

			// add special types 
			s_DefaultContract.AddKnownType(typeof(IJsonSerializable));
			s_DefaultContract.AddKnownType(typeof(byte[]));
			s_DefaultContract.AddKnownType(typeof(IDictionary));
			s_DefaultContract.AddKnownType(typeof(IEnumerable));
			s_DefaultContract.AddKnownType(typeof(IDictionary<string, object>));
			s_DefaultContract.AddKnownType(typeof(System.IO.Stream));
			s_DefaultContract.AddKnownType(typeof(Serializers.JsonStringSerializer));
			s_DefaultContract.AddKnownType(typeof(Serializers.DateTimeSerializer));
			s_DefaultContract.AddKnownType(typeof(Serializers.DateTimeOffsetSerializer));
			s_DefaultContract.AddKnownType(typeof(Serializers.GuidSerializer));
			s_DefaultContract.AddKnownType(typeof(Serializers.UriSerializer));
			for (int i = 0; i < s_Primitives.Count; i++)
				s_DefaultContract.GetSerializerOfType(s_Primitives[i]);

			s_AdHocContract = new DataContract(s_DefaultContract);
		}

		public static DataContract FetchFromCache(object cacheKey)
		{
			if (cacheKey == null)
				throw new ArgumentNullException("cacheKey");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var ctr = s_Cache.FetchValueOrDefault(cacheKey);
			return ctr;
		}
		public static void PutInCache(object cacheKey, DataContract contract)
		{
			if (cacheKey == null)
				throw new ArgumentNullException("cacheKey");
			if (contract == null)
				throw new ArgumentNullException("contract");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			s_Cache.ExchangeValue(cacheKey, contract);
		}
		public static DataContract FetchOrCreate(object cacheKey, Func<DataContract> creator)
		{
			if (cacheKey == null)
				throw new ArgumentNullException("cacheKey");
			if (creator == null)
				throw new ArgumentNullException("creator");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			return s_Cache.FetchOrCreateValue(cacheKey, creator);
		}
		public static DataContract FetchOrCreate(Type serializableType)
		{
			if (serializableType == null)
				throw new ArgumentNullException("serializableType");
			System.Diagnostics.Contracts.Contract.EndContractBlock();


			var contract = default(DataContract);
			contract = s_Cache.FetchValueOrDefault(serializableType);
			if (contract != null)
				return contract;

			return s_Cache.FetchOrCreateValue(serializableType, () => new DataContract(serializableType, null, MetadataInterpreter.Default, MetadataDescriber.Default));
		}
	}
}
